// FtpConnection.cpp: implementation of the CFtpConnection class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "FtpConnection.h"
#include "Output.h"
#include "ftp_exception.h"
#include "SocketConnection.h"
#include "FtpDownload.h"
#include <io.h>
#include <process.h>



using namespace std;
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

COutput output;

void DownloadThread( void* pArguments )
{
    CFtpDownload* pDownload = (CFtpDownload *)pArguments;
	CSocketConnection socket;
	socket.Connect(pDownload->ip.c_str(), pDownload->port.c_str());
	socket.SetSocketOpt(SOL_SOCKET,SO_KEEPALIVE,NULL,0);

	const int BUFSIZE = 100 * 1000;
	char buf[BUFSIZE];

	int recvLen = socket.Receive(buf, BUFSIZE, 60);

	while(recvLen > 0)
	{
		fwrite(buf, sizeof(char), recvLen, pDownload->pTarget);
		recvLen = socket.Receive(buf, BUFSIZE, 60);
	}
	
	fclose(pDownload->pTarget);

	CFtpConnection* pFtp = (CFtpConnection *)pDownload->pFtp;
	pFtp->OnTransferComplete();

	delete pDownload;

	_endthreadex( 0 );

    return;
} 



CFtpConnection::CFtpConnection(CSocketConnection *pSocket)
{
	m_pSocket = pSocket;
}

CFtpConnection::~CFtpConnection()
{

}

void CFtpConnection::Connect(string ip, UINT port, string username, string password)
{
	//Connect to socket
	const int BUFSIZE = 100;
	char szPort[10];
	int iRet;

	iRet = _itoa_s(port, szPort, 10, 10);
	
	if(iRet != 0)
	{
		throw ftp_exception("Port incorrect!");
	}

	if(m_pSocket == NULL)
	{
		throw  ftp_exception("Invalid socket");
	}

	output << "Connecting to " + ip + ":" + szPort;
	m_pSocket->Connect(ip.c_str(), szPort);
	output << "Connection established, waiting for welcome message...";

	//Receive welcome message
	char buffer[BUFSIZE];
	int recvLen = m_pSocket->Receive(buffer, BUFSIZE - 1);
	AddEndChar(buffer, recvLen);
	string code = GetCode(buffer);
	
	if(code != "220")
	{
		throw  ftp_exception("Can not receive welcome message");
	}
	output << buffer;


	//Send username
	string szSend = "USER " + username + "\r\n";
	m_pSocket->Send(szSend.c_str(), (int)szSend.length());

	recvLen = m_pSocket->Receive(buffer, BUFSIZE - 1);
	AddEndChar(buffer, recvLen);
	code = GetCode(buffer);

	if(code == "331")
	{
		//Send password
		szSend = "PASS " + password + "\r\n";
		m_pSocket->Send(szSend.c_str(), (int)szSend.length());

		recvLen = m_pSocket->Receive(buffer, BUFSIZE - 1);
		AddEndChar(buffer, recvLen);
		code = GetCode(buffer);

		if(code == "230")
		{
			output << buffer;
		}
		else
		{
			output << buffer;

			return;
		}
	}
	
	//Change to binary mode
	szSend = "TYPE I\r\n";
	m_pSocket->Send(szSend.c_str(), (int)szSend.length());

	recvLen = m_pSocket->Receive(buffer, BUFSIZE - 1);
	AddEndChar(buffer, recvLen);
	output << buffer;

}

string CFtpConnection::GetCode(string str)
{
	string szCode;
	int index = (int)str.find(' ');
	
	if(index == string::npos)
	{
		throw  ftp_exception("can not get code");
	}

	szCode = str.substr(0, index);

	return szCode;
}

void CFtpConnection::AddEndChar(char *pBuffer, int len)
{
	if(len <= 0)
	{
		throw  ftp_exception("No data received");
	}

	pBuffer[len] = '\0';
}

void CFtpConnection::ListCurrentDir(char* dirBuf, int bufLen)
{
	const int BUFSIZE = 100;
	char recvBuf[BUFSIZE];

	//PASV
	string send = "PASV\r\n";
	m_pSocket->Send(send.c_str(), (int)send.length());

	int recvLen = m_pSocket->Receive(recvBuf, BUFSIZE - 1);
	AddEndChar(recvBuf, recvLen);
	string code = GetCode(recvBuf);
	output << recvBuf;

	if(code != "227")
	{
		//do not support pasv
		output << "Do no support PASV";
		return;
	}

	string ip, port;
	ParsePasvString(recvBuf, &ip, &port);

	//Create data connection
	CSocketConnection dataConn;
	dataConn.Connect(ip.c_str(), port.c_str());
	output << "Connect to server: " + ip + ":" + port;

	//List directory
	send = "LIST \r\n";
	m_pSocket->Send(send.c_str(), (int)send.length());
	recvLen = m_pSocket->Receive(recvBuf, BUFSIZE - 1);
	AddEndChar(recvBuf, recvLen);
	code = GetCode(recvBuf);
	if(code == "150")
	{
		//Get the directory list
		recvLen = dataConn.Receive(dirBuf, bufLen - 1);
		if(recvLen == BUFSIZE - 1)
		{
			throw ftp_exception("List directory out of buffer");
		}

		AddEndChar(dirBuf, recvLen);
	}

	//Transfer complete
	recvLen = m_pSocket->Receive(recvBuf, BUFSIZE - 1);
	AddEndChar(recvBuf, recvLen);
	output << recvBuf;
}

void CFtpConnection::DownloadFile(string filename, FILE* pTargetFile)
{
	const int BUFSIZE = 100;
	char recvBuf[BUFSIZE];

	//PASV
	string send = "PASV\r\n";
	m_pSocket->Send(send.c_str(), (int)send.length());

	int recvLen = m_pSocket->Receive(recvBuf, BUFSIZE - 1);
	AddEndChar(recvBuf, recvLen);
	string code = GetCode(recvBuf);
	output << recvBuf;

	if(code != "227")
	{
		//do not support pasv
		output << "Do no support PASV";
		return;
	}

	string ip, port;
	ParsePasvString(recvBuf, &ip, &port);

	CFtpDownload* pDownload = new CFtpDownload();

	pDownload->ip = ip;
	pDownload->port = port;
	pDownload->pTarget = pTargetFile;
	pDownload->pFtp = this;

	_beginthread(&DownloadThread, NULL, pDownload);

	//Retrieve
	send = "RETR " + filename + "\r\n";
	m_pSocket->Send(send.c_str(), (int)send.length());
	recvLen = m_pSocket->Receive(recvBuf, BUFSIZE - 1);
	AddEndChar(recvBuf, recvLen);
	code = GetCode(recvBuf);
	if(code == "150")
	{
		//start download
	}

	//Transfer complete
	//recvLen = m_pSocket->Receive(recvBuf, BUFSIZE - 1);
	//AddEndChar(recvBuf, recvLen);
	//output << recvBuf;
}


void CFtpConnection::ChangeCurrentDir(string dir)
{
	string send = "CWD " + dir + "\r\n";
	m_pSocket->Send(send.c_str(), (int)send.length());

	char buf[100];
	int recvLen = m_pSocket->Receive(buf, 99);
	AddEndChar(buf, recvLen);
	output << buf;

	if(GetCode(buf) != "250")
	{
		throw ftp_exception("Change dir failed");
	}
}

void CFtpConnection::OnTransferComplete()
{
	const int BUFSIZE = 100;
	char buf[BUFSIZE];
	int recvLen = m_pSocket->Receive(buf, BUFSIZE - 1, 60);
	if(recvLen > 0)
	{
		AddEndChar(buf, recvLen);
		output << buf;
	}
}

void CFtpConnection::GetCurrentDir(char* dir, int bufLen)
{
	string send = "PWD\r\n";
	m_pSocket->Send(send.c_str(), (int)send.length());

	const int BUFSIZE = 100;
	char buf[BUFSIZE];
	int recvLen = m_pSocket->Receive(buf, BUFSIZE - 1);
	AddEndChar(buf, recvLen);
	output << buf;
	if(GetCode(buf) != "257")
	{
		throw ftp_exception("List dir failed");
	}

	string s(buf);
	size_t start = s.find_first_of('\"');
	size_t end = s.find_last_of('\"');
	//trim code and quote
	s = s.substr(start + 1, end - start - 1);

	strcpy_s(dir, bufLen, s.c_str());
}

void CFtpConnection::ParsePasvString(const char* buf, string* ip, string* port)
{
	//Retrieve pasv ip string like xx,xx,xx,xx,xx,xx
	string s(buf);
	size_t start = s.find_first_of('(');
	size_t end = s.find_last_of(')');

	if(start == string::npos || end == string::npos || start >= end)
	{
		throw ftp_exception("can not parse pasv string");
	}

	//port
	string sub = s.substr(start + 1, end - start - 1);
	start = sub.find_last_of(',');
	unsigned char lowPort = atoi(sub.substr(start + 1, sub.length() - start - 1).c_str());
	sub = sub.substr(0, start);
	start = sub.find_last_of(',');
	unsigned char highPort = atoi(sub.substr(start + 1, sub.length() - start - 1).c_str());
	sub = sub.substr(0, start);
	int iPort = highPort * 256 + lowPort;
	char temp[10];
	_itoa_s(iPort, temp, 10, 10);
	*port = temp;

	//ip
	start = sub.find_first_of(',');
	*ip = sub.substr(0, start);
	sub = sub.substr(start + 1, sub.length() - start - 1);

	start = sub.find_first_of(',');
	while(start != string::npos)
	{
		*ip = *ip + "." + sub.substr(0, start);
		sub = sub.substr(start + 1, sub.length() - start - 1);
		start = sub.find_first_of(',');
	}

	*ip = *ip + "." + sub;
}

void CFtpConnection::DownloadFile(string filename)
{
	//Check if the ftp server support multiple thread
	//int numOfThread = 5;
	//int minBlock = 100 * 1024;
	//string rest = "REST 100 \r\n";
	//char buffer[100];
	//int recLen ;
	//output << rest;
	//m_pSocket->Send(rest.c_str(),rest.Length());
	//recLen = m_pSocket->Receive(buffer,99);
	//AddEndChar(buffer, recLen);
	//output << buffer;

	//if(GetCode(buffer) != "350")
	//{
	//   numOfThread = 1;		//Use only 1 thread
	//   output << "This server do not support multiple thread, use only 1";
	//}

	//this->fileSize = GetFileSize(filename);

	//if(filesize < minBlock)
	//{
	//	numOfThread = 1;
	//}

	//string hisFileName = filename + ".his";
	//string tmpFileName = filename + ".tmp";

 //  if(_access(hisFileName.c_str(), 0) == 0) 
 //  {
	//   //File exist
 //     //this->GetInfor(hisFileName);
 //  }
 //  else
 //  {
 //    globalFile = fopen(hisFileName.c_str(),"w+b");
 //    if(globalFile == NULL)
 //    {
	//	 throw ftp_exception("Open file failed");
 //    }

 //    this->FilePos = this->perFilePos =  0;
 //    this->CreateNewFile(tmpFileName,this->fileSize);
 //    DivisionFile();
 //  }
}

unsigned long long CFtpConnection::GetFileSize(string filename)
{
   string  size = "SIZE " + filename + "\r\n";
   output << size;
   m_pSocket->Send(size.c_str(), (int)size.length());

   const int BUFSIZE = 100;
   char buffer[BUFSIZE];
   int recLen ;
   recLen = m_pSocket->Receive(buffer, BUFSIZE - 1);
   AddEndChar(buffer, recLen);
   output << buffer;

   if(GetCode(buffer) != "213")
   {
	   throw ftp_exception("Failed to get file size");
   }

   string recvStr(buffer);

   size_t index =  recvStr.find("\r\n");
   recvStr = recvStr.substr(1,index-1);
   index = recvStr.find(" ");
   recvStr = recvStr.substr(index+1,recvStr.length()- index);
   
   return _atoi64(recvStr.c_str());
}

//bool CFtpConnection::CreateNewFile(string fileName, unsigned long long size)
//{
//   string tempFileName = fileName + ".tmp";
//   FILE  *file;
//   file = fopen(tempFileName.c_str(),"w+b");
//   if(file == NULL) return false;
//   
//   char * buffer  = new char[60000];
//   memset(buffer,'0',60000);
//   DWORD writeLen = 0;
//   int needLen = 60000;
//   while(writeLen < size)
//   {
//     if(writeLen + 60000 > size)
//     {
//        needLen = size -writeLen;
//     }
//     else
//        needLen = 60000;
//     int len =  fwrite(buffer,sizeof(char),needLen,file);
//     if(len > 0)  writeLen += len;
//  }
//  delete[]  buffer;
//    fclose(file);
//    return true;
//}
